package cn.pso.nspsofs;


import cn.read.ReadFile;
import cn.read.Util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import static java.util.Collections.swap;
import static java.util.stream.Collectors.toList;

public class Process_NSPSOFS {
    public static int N_VAR = 20;
    public static int N_OBJ = 3;
    // 粒子群
    Particle_NSPSOFS[] swarm;
    // 全局最优解
    double[] global_best;
    // 粒子的数量
    int p_count;
    List<Particle_NSPSOFS> F1 = new ArrayList<>();

    private FN fn;

    private boolean enableNcd;

    private boolean enableMutate;


    public void setFn(FN fn) {
        this.fn = fn;
    }

    public void setEnableNcd(boolean enableNcd) {
        this.enableNcd = enableNcd;
    }

    public void setEnableMutate(boolean enableMutate) {
        this.enableMutate = enableMutate;
    }

    public static String name;

    /**
     * 排序
     *
     * @param leaderSet LeaderSet
     * @return List<Particle_NSPSOFS>
     */
    public List<Particle_NSPSOFS> sortSet(List<Particle_NSPSOFS> leaderSet) {
        Comparator<Particle_NSPSOFS> comparing = Comparator.comparing(p -> p.fitness[0]);
        leaderSet.sort(comparing.reversed());
        return leaderSet;
    }

    /**
     * 粒子群初始化
     *
     * @param numOfSwarm 粒子的数量
     * @throws IOException IOException
     */
    public void init(int numOfSwarm, String name) throws IOException {
        Process_NSPSOFS.name = name;
        p_count = numOfSwarm;
        swarm = new Particle_NSPSOFS[p_count];  //粒子群
        global_best = new double[N_OBJ];

        assert fn != null;

        //类的静态成员的初始化
        Particle_NSPSOFS.c1 = 1.49618;
        Particle_NSPSOFS.c2 = 1.49618;
        Particle_NSPSOFS.w = 0.7298;
        Particle_NSPSOFS.dims = N_VAR;


        for (int i = 0; i < p_count; ++i) {
            swarm[i] = new Particle_NSPSOFS();
            swarm[i].initial(N_VAR, N_OBJ, fn);
            swarm[i].evaluate(fn);
        }
    }


    /**
     * 粒子群的运行
     *
     * @throws IOException IOException
     */
    public void run(int totalRunTimes, int times) throws IOException {
        boolean isDomination;
        int count = 1;
        SortBest_NSPSOFS sb = new SortBest_NSPSOFS();
        List<Particle_NSPSOFS> record;
        int remainRunTimes = totalRunTimes;
        while (remainRunTimes > 0) {
            // 非支配粒子集合
            List<Particle_NSPSOFS> nonDomS = new ArrayList<>();

            // 求出nonDomS
            for (int i = 0; i < p_count; i++) {
                // 如果当前的swarm 没有支配所有的swarm 则isDomination=false，不添加到nonDoms中
                isDomination = true;
                for (int j = 0; j < p_count; j++) {
                    if (swarm[j].pbest_fitness[0] <= swarm[i].pbest_fitness[0] && swarm[j].pbest_fitness[1] < swarm[i].pbest_fitness[1]) {
                        isDomination = false;
                    } else if (swarm[j].pbest_fitness[0] < swarm[i].pbest_fitness[0] && swarm[j].pbest_fitness[1] <= swarm[i].pbest_fitness[1]) {
                        isDomination = false;
                    }
                }

                // 如果当前的swarm 已经在nonDoms中存在 不添加到nonDoms中
                for (Particle_NSPSOFS nonDom : nonDomS) {
                    if (nonDom.pbest_fitness[0] == swarm[i].pbest_fitness[0] && nonDom.pbest_fitness[1] == swarm[i].pbest_fitness[1]) {
                        isDomination = false;
                        break;
                    }
                }

                if (isDomination) {
                    nonDomS.add(swarm[i]);
                }
            }

            // 将nonDomS中的粒子按照位置排序
            nonDomS = sortSet(nonDomS);

            // 求拥挤距离,按拥挤距离排序
            List<Particle_NSPSOFS> temp = nonDomS;
            double firstGbestD = 0;
            for (int i = 0; i < nonDomS.size() - 1; i++) {
                double cd_i;
                if (i == 0) {
                    cd_i = Double.POSITIVE_INFINITY;//第一个和最后一个粒子拥挤距离为无穷大
                } else {
                    cd_i = getCd(temp, i);
                }

                for (int j = i + 1; j < nonDomS.size(); ++j) {
                    double cd_j;
                    if (j == nonDomS.size() - 1) {
                        cd_j = Double.POSITIVE_INFINITY;//第一个和最后一个粒子拥挤距离为无穷大
                    } else {
                        cd_j = getCd(temp, j);
                    }
                    if (i == 1) {
                        firstGbestD = cd_i;
                    }
                    if (cd_i > cd_j) {
                        swap(nonDomS, i, j);
                        if (i == 1) {
                            firstGbestD = cd_j;
                        }
                    }
                }
            }

            // 将swarm中的粒子添加到union
            Particle_NSPSOFS[] kl = swarm.clone();
            List<Particle_NSPSOFS> union = new ArrayList<>(Arrays.asList(kl));

            //求拥挤距离最小的粒子的集合
            List<Particle_NSPSOFS> gbestD = new ArrayList<>();
            for (int i = 0; i < nonDomS.size(); i++) {
                double di;
                // 第一个和最后一个粒子拥挤距离为无穷大
                if (i == 0 || i == nonDomS.size() - 1) {
                    di = Double.POSITIVE_INFINITY;
                } else {
                    di = getCd(temp, i);
                }
                if (firstGbestD == di) {
                    gbestD.add(temp.get(i));
                }
            }

            if (gbestD.size() == 0) {
                gbestD.add(nonDomS.get(0));
            }

            //每个粒子更新位置和适应值
            for (Particle_NSPSOFS particle_nspsofs : swarm) {
                if (gbestD.size() == 1) {
                    particle_nspsofs.gbest_fitness = gbestD.get(0).pbest_fitness;
                    particle_nspsofs.gbest = gbestD.get(0).pbest;
                } else {
                    int rand = Util.randomNum(0, gbestD.size(), 1)[0];
                    particle_nspsofs.gbest_fitness = gbestD.get(rand).pbest_fitness;
                    particle_nspsofs.gbest = gbestD.get(rand).pbest;
                }
                particle_nspsofs.updateV();
                if (enableMutate) {
                    particle_nspsofs.mutate(remainRunTimes, totalRunTimes);
                }
                particle_nspsofs.evaluate(fn);
                union.add(particle_nspsofs);
            }

            //求F1
            F1.clear();
            for (int i = 0; i < union.size(); ++i) {
                isDomination = true;
                for (Particle_NSPSOFS particle_nspsofs : union) {
                    if (particle_nspsofs.pbest_fitness[0] <= union.get(i).pbest_fitness[0] && particle_nspsofs.pbest_fitness[1] < union.get(i).pbest_fitness[1]) {
                        isDomination = false;
                    } else if (particle_nspsofs.pbest_fitness[0] < union.get(i).pbest_fitness[0] && particle_nspsofs.pbest_fitness[1] <= union.get(i).pbest_fitness[1]) {
                        isDomination = false;
                    }
                }

                for (Particle_NSPSOFS particle_nspsofs : F1) {
                    if (particle_nspsofs.pbest_fitness[0] == union.get(i).pbest_fitness[0] && particle_nspsofs.pbest_fitness[1] == union.get(i).pbest_fitness[1]) {
                        isDomination = false;
                        break;
                    }
                }


                if (isDomination) {
                    F1.add(union.get(i));
                }
            }
            /*
             * 将Union中不受控制粒子加到F1集合中并把这些粒子从Union中除去，
             * 再在新的Union中把不受控制粒子加到F2中并把这些粒子从Union中除去，
             * 重复步骤得到F=(F1,F2,F3...)
             */
            swarm = new Particle_NSPSOFS[p_count];
            List<Particle_NSPSOFS> Fi = new ArrayList<>();
            List<Particle_NSPSOFS> swarmTemp = new ArrayList<>();
            INNER:
            while (true) {
                for (int i = 0; i < union.size(); ++i) {
                    isDomination = true;
                    for (Particle_NSPSOFS particle_nspsofs : union) {
                        if (particle_nspsofs.pbest_fitness[0] <= union.get(i).pbest_fitness[0] && particle_nspsofs.pbest_fitness[1] < union.get(i).pbest_fitness[1]) {
                            isDomination = false;
                        } else if (particle_nspsofs.pbest_fitness[0] < union.get(i).pbest_fitness[0] && particle_nspsofs.pbest_fitness[1] <= union.get(i).pbest_fitness[1]) {
                            isDomination = false;
                        }
                    }

                    for (Particle_NSPSOFS particle_nspsofs : Fi) {
                        if (particle_nspsofs.pbest_fitness[0] == union.get(i).pbest_fitness[0] && particle_nspsofs.pbest_fitness[1] == union.get(i).pbest_fitness[1]) {
                            isDomination = false;
                            break;
                        }
                    }

                    if (isDomination) {
                        Fi.add(union.get(i));
                        union.remove(i);
                    }
                }

                Fi = sortSet(Fi);
                temp = Fi;

                for (int i = 0; i < Fi.size(); ++i) {
                    double cd_i;
                    if (i == 0 || i == Fi.size() - 1) {
                        cd_i = Double.POSITIVE_INFINITY;//第一个和最后一个粒子拥挤距离为无穷大
                    } else {
                        cd_i = getCd(temp, i);
                    }
                    for (int j = i + 1; j < Fi.size(); ++j) {
                        double cd_j;
                        if (j == 0 || j == Fi.size() - 1) {
                            cd_j = Double.POSITIVE_INFINITY;//第一个和最后一个粒子拥挤距离为无穷大
                        } else {
                            cd_j = getCd(temp, j);
                        }
                        if (cd_i > cd_j) {
                            swap(Fi, i, j);
                        }
                    }
                }

                for (Particle_NSPSOFS i : Fi) {
                    swarmTemp.add(i);
                    if (swarmTemp.size() == swarm.length) {
                        for (int j = 0; j < swarmTemp.size(); j++) {
                            swarm[j] = swarmTemp.get(j);
                        }
                        break INNER;
                    }
                }
            }
            record = F1;
            if (count % 10 == 0 || count == 1) {
                sb.getNSResult(Process_NSPSOFS.name, record, count + "-" + times);
            }
            count++;
            remainRunTimes--;
        }
    }

    private double getCd(List<Particle_NSPSOFS> particleList, int index) throws IOException {
        // TODO(tianxing): there can be configurable
        if (enableNcd) {
            return getNcd(particleList, index);
        } else {
            return getCd0(particleList, index);
        }
    }

    private double getCd0(List<Particle_NSPSOFS> particleList, int index) throws IOException {
        return (particleList.get(index + 1).pbest_fitness[0] - particleList.get(index - 1).pbest_fitness[0]) / ReadFile.getFeatureNum(Process_NSPSOFS.name)
                + particleList.get(index + 1).pbest_fitness[1] - particleList.get(index - 1).pbest_fitness[1];
    }

    private double getNcd(List<Particle_NSPSOFS> particleList, int index) throws IOException {
        double[] pbest_fitness = particleList.get(index).pbest_fitness;
        return ((pbest_fitness[0] - 0) * (1 - pbest_fitness[1]) + (pbest_fitness[1] - 0) * (ReadFile.getFeatureNum(Process_NSPSOFS.name) - pbest_fitness[0]))
                // simplify
                // / (ReadFile.getFeatureNum(Process_NSPSOFS.name) - 0) * (1 - 0);
                / (ReadFile.getFeatureNum(Process_NSPSOFS.name));
    }


    /**
     * 显示程序求解结果
     */
    public void showResult(List<Particle_NSPSOFS> list) {
        INNER:
        for (Particle_NSPSOFS particle_nspsofs : F1) {
            int k = 0;
            for (Particle_NSPSOFS p : list) {
                if (p.pbest_fitness[0] == particle_nspsofs.pbest_fitness[0]) {
                    if (p.pbest_fitness[1] <= particle_nspsofs.pbest_fitness[1]) {
                        continue INNER;
                    } else {
                        k = list.indexOf(p);
                    }
                }
            }
            if (k > 0) {
                list.remove(k);
            }
            list.add(particle_nspsofs);
        }
    }

    /**
     * 计算igd
     */
    public double calcIgd() {
        List<double[]> fitnessList = Arrays.stream(swarm).map(p -> p.pbest_fitness).collect(toList());
        List<double[]> realPf = fn.readRealPf();

        return fn.doCalcIgd(fitnessList, realPf);
    }
}
